#ifndef __MY_SERVICE__
#define __MY_SERVICE__
#include "data_manage.hpp"
#include "httplib.h"

extern cloud::DataManager *data_;
extern MYSQL *mysql_;
extern sw::redis::Redis *redis_;
namespace cloud
{
    class service
    {
    public:
        service()
        {
            xqlog::GetLogger("asynclogger")->Info("Service start(Construct)");

            server_port_ = Config::GetInstance()->GetServerPort();
            server_ip_ = Config::GetInstance()->GetServerIp();
            download_prefix_ = Config::GetInstance()->GetDownloadPrefix();

            xqlog::GetLogger("asynclogger")->Info("Service end(Construct)");
        }
        bool RunModule()
        {
            // 下载文件请求
            std::string download_path_ = download_prefix_ + "(.*)"; // 下载前缀+正则表达式表示下载备份文件的路径
            server_.Get(download_path_, Download);
            // 展示已备份文件信息请求
            server_.Get("/", ListShow);
            server_.Get("/listshow", ListShow);
            // 上传文件请求
            server_.Post("/upload", Upload);
            if (server_.listen(server_ip_.c_str(), server_port_) == false)
            {
                xqlog::GetLogger("asynclogger")->Fatal("server listen error:server_ip-%s;server_port-%s", server_ip_, server_port_);
            }
            return true;
        }

    private:
        int server_port_;
        std::string server_ip_;
        std::string download_prefix_;
        httplib::Server server_;

    private:
        static void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            xqlog::GetLogger("asynclogger")->Info("Upload start");
            // post /upload  文件数据在正文中（正文并不全是文件数据）
            auto ret = req.has_file("file"); // 约定：判断请求报头中是否包含"file"字段，有就说明请求报文中存在文件数据
            if (ret == false)
            {
                rsp.status = 400; // 设置响应状态码
                return;
            }

            const auto &file = req.get_file_value("file");
            // file.filename//文件名称    file.content//文件数据
            FileUtil tmp1(Config::GetInstance()->GetBackupDir());
            tmp1.CreateDirectory();

            std::string backpath = Config::GetInstance()->GetBackupDir() + FileUtil(file.filename).FileName();
            FileUtil fu(backpath);

            // 从Redis查询是否存在，存在则返回，不存在则去MySQL再往下走
            auto value = redis_->exists(fu.FileName().c_str());
            // 存在则返回1，直接返回
            if (value)
            {
                xqlog::GetLogger("asynclogger")->Info("redis: %s exists", fu.FileName().c_str());
                return;
            }

            // redis中没查到, 从MySQL查询文件是否已经存在
            std::string stmt_str = "select * from backup_file where file_name=";
            stmt_str += "\'" + fu.FileName() + "\'";
            unsigned long length = stmt_str.size();
            mysql_real_query(mysql_, stmt_str.c_str(), length);

            MYSQL_RES *res = mysql_store_result(mysql_);
            int rows = mysql_num_rows(res);
            if (rows > 0)
            {
                xqlog::GetLogger("asynclogger")->Info("mysql: %s exists", fu.FileName().c_str());
                return;
            }

            // 释放结果集以便下一步的mysql插入操作
            mysql_free_result(res);

            fu.SetContent(file.content); // 将数据写入文件中；

            // 添加备份文件信息，交由数据管理类进行管理
            BackupInfo info;
            info.NewBackupInfo(backpath); // 组织备份的文件信息
            data_->Insert(info);          // 向数据管理模块添加备份的文件信息

            // 向redis中添加文件路径信息
            xqlog::GetLogger("asynclogger")->Info("redis set backup information start");
            bool rset_ret = redis_->set(fu.FileName().c_str(), Config::GetInstance()->GetBackupDir().c_str());
#ifdef DEBUG_LOG
            if (rset_ret)
                xqlog::GetLogger("asynclogger")->Debug("redis set backup information success");
            else
                xqlog::GetLogger("asynclogger")->Debug("redis set backup information fail");
#endif
            // 向数据库添加文件路径信息
            xqlog::GetLogger("asynclogger")->Info("mysql insert backup information start");
            stmt_str = "insert into backup_file values(";
            stmt_str += "\'" + fu.FileName() + "\'" + "," + "\'" + Config::GetInstance()->GetBackupDir() + "\'" + ")";
            length = stmt_str.size();
            if (mysql_real_query(mysql_, stmt_str.c_str(), length) != 0)
            {
                xqlog::GetLogger("asynclogger")->Info("mysql insert backup information fail:%d", mysql_errno(mysql_));
            }
        }

        static std::string TimetoStr(time_t t)
        {
            std::string tmp = std::ctime(&t);
            return tmp;
        }

        static void ListShow(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1. 获取所有的文件备份信息
            std::vector<BackupInfo> arry;
            data_->GetAll(&arry);

            // 2. 根据备份信息，组织html文件数据
            std::stringstream ss;
            ss << "<html><head><title>Download</title></head>";
            ss << "<body><h1>Download</h1><table>";
            for (auto &a : arry)
            {
                ss << "<tr>";
                std::string filename = FileUtil(a.real_path_).FileName();
                ss << "<td><a href='" << a.url_ << "'>" << filename << "</a></td>";
                ss << "<td align='right'>" << TimetoStr(a.mtime_) << "</td>";
                ss << "<td align='right'>" << a.fsize_ / 1024 << "k</td>";
                ss << "</tr>";
            }
            ss << "</table></body></html>";
            rsp.body = ss.str();
            rsp.set_header("Content-Type", "text/html;charset=utf-8"); // 指定解析格式为html，编码utf-8
            rsp.status = 200;
        }
        static std::string GetETag(const BackupInfo &info)
        {
            // etg :  filename-fsize-mtime
            FileUtil fu(info.real_path_);
            std::string etag = fu.FileName();
            etag += "-";
            etag += std::to_string(info.fsize_);
            etag += "-";
            etag += std::to_string(info.mtime_);
            return etag;
        }
        static void Download(const httplib::Request &req, httplib::Response &rsp)
        {
            // 1. 获取客户端请求的资源路径path   req.path
            // 2. 根据资源路径，获取文件备份信息
            BackupInfo info;
            data_->GetOneByURL(req.path, &info);

            // 3. 判断文件是否被压缩，如果被压缩，要先解压缩
            if (info.pack_flag_ == true)
            {
                FileUtil fu(info.pack_path_);
                fu.UnCompress(info.real_path_); // 将文件解压到备份目录下
                // 4. 删除压缩包，修改备份信息（已经没有被压缩）
                remove(info.pack_path_.c_str());
                info.pack_flag_ = false;
                data_->Update(info);
            }

            // 确认文件是否需要断点续传
            bool retrans = false;
            std::string old_etag;
            if (req.has_header("If-Range"))
            {
                old_etag = req.get_header_value("If-Range");
                // 有If-Range字段且，这个字段的值与请求文件的最新etag一致则符合断点续传
                if (old_etag == GetETag(info))
                {
                    retrans = true;
                }
            }

            // 4. 读取文件数据，放入rsp.body中
            FileUtil fu(info.real_path_);
            if (retrans == false)
            {
                fu.GetContent(&rsp.body);
                // 5. 设置响应头部字段： ETag， Accept-Ranges: bytes
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("ETag", GetETag(info));
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.status = 200;
            }
            else
            {
                // httplib内部实现了对于区间请求也就是断点续传请求的处理
                // 只需要我们用户将文件所有数据读取到rsp.body中，它内部会自动根据请求
                // 区间，从body中取出指定区间数据进行响应
                //  std::string  range = req.get_header_val("Range"); bytes=start-end
                fu.GetContent(&rsp.body);
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("ETag", GetETag(info));
                rsp.set_header("Content-Type", "application/octet-stream");
                // rsp.set_header("Content-Range", "bytes start-end/fsize");
                rsp.status = 206; // 区间请求响应的是206*****
            }
        }
    };
}

#endif